// 版权所有2010 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package gzip

import (
	"compress/flate"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"time"
)

// 这些常量是从flate包复制的，因此导入
// /“compress/gzip”的代码也不必导入“compress/flate”。
const (
	NoCompression      = flate.NoCompression
	BestSpeed          = flate.BestSpeed
	BestCompression    = flate.BestCompression
	DefaultCompression = flate.DefaultCompression
	HuffmanOnly        = flate.HuffmanOnly
)

// 作家就是一个io。WriteCloser。
// 对写入程序的写入被压缩并写入w。
type Writer struct {
	Header      // 在第一次调用写入、刷新或关闭时写入
	w           io.Writer
	level       int
	wroteHeader bool
	compressor  *flate.Writer
	digest      uint32 // CRC-32，IEEE多项式（第8节）
	size        uint32 // 未压缩大小（第2.3.1节）
	closed      bool
	buf         [10]byte
	err         error
}

// NewWriter返回一个新的写入程序。
// 对返回的写入程序的写入被压缩并写入w。
// 
// 完成后，调用方有责任关闭写入程序。
// 写入操作可能会被缓冲，直到关闭后才会刷新。
// 
// 希望在Writer中设置字段的调用方。头必须在
// 写入、刷新或关闭的第一个调用之前执行。
func NewWriter(w io.Writer) *Writer {
	z, _ := NewWriterLevel(w, DefaultCompression)
	return z
}

// NewWriterLevel与NewWriter类似，但它指定了压缩级别，而不是假设默认压缩的
// 。压缩级别可以是DefaultCompression、NoCompression、HuffmanOnly或BestSpeed和BestCompression之间的任意整数值。
// 如果级别有效，返回的错误将为零。
func NewWriterLevel(w io.Writer, level int) (*Writer, error) {
	if level < HuffmanOnly || level > BestCompression {
		return nil, fmt.Errorf("gzip: invalid compression level: %d", level)
	}
	z := new(Writer)
	z.init(w, level)
	return z, nil
}

func (z *Writer) init(w io.Writer, level int) {
	compressor := z.compressor
	if compressor != nil {
		compressor.Reset(w)
	}
	*z = Writer{
		Header: Header{
			OS: 255, // 未知
		},
		w:          w,
		level:      level,
		compressor: compressor,
	}
}

// 重置放弃写入程序z的状态，并使其等同于NewWriter或NewWriterLevel的原始状态的结果，但
// 改为写入w。这允许重复使用编写器，而不是分配新的编写器。
func (z *Writer) Reset(w io.Writer) {
	z.init(w, z.level)
}

// writeBytes将长度前缀字节片写入z.w。
func (z *Writer) writeBytes(b []byte) error {
	if len(b) > 0xffff {
		return errors.New("gzip.Write: Extra data is too large")
	}
	le.PutUint16(z.buf[:2], uint16(len(b)))
	_, err := z.w.Write(z.buf[:2])
	if err != nil {
		return err
	}
	_, err = z.w.Write(b)
	return err
}

// writeString将GZIP格式的UTF-8字符串s写入z.w。
// GZIP（RFC 1952）指定字符串以NUL结尾的ISO 8859-1（拉丁文-1）。
func (z *Writer) writeString(s string) (err error) {
	// GZIP存储拉丁-1字符串；如果不是拉丁语-1，则为错误；如果不是ASCII，则进行转换。
	needconv := false
	for _, v := range s {
		if v == 0 || v > 0xff {
			return errors.New("gzip.Write: non-Latin-1 header string")
		}
		if v > 0x7f {
			needconv = true
		}
	}
	if needconv {
		b := make([]byte, 0, len(s))
		for _, v := range s {
			b = append(b, byte(v))
		}
		_, err = z.w.Write(b)
	} else {
		_, err = io.WriteString(z.w, s)
	}
	if err != nil {
		return err
	}
	// GZIP字符串以NUL结尾。
	z.buf[0] = 0
	_, err = z.w.Write(z.buf[:1])
	return err
}

// Write将p的压缩形式写入底层io。作家
// 压缩字节在写入程序关闭之前不一定刷新。
func (z *Writer) Write(p []byte) (int, error) {
	if z.err != nil {
		return 0, z.err
	}
	var n int
	// 延迟写入GZIP头。
	if !z.wroteHeader {
		z.wroteHeader = true
		z.buf = [10]byte{0: gzipID1, 1: gzipID2, 2: gzipDeflate}
		if z.Extra != nil {
			z.buf[3] |= 0x04
		}
		if z.Name != "" {
			z.buf[3] |= 0x08
		}
		if z.Comment != "" {
			z.buf[3] |= 0x10
		}
		if z.ModTime.After(time.Unix(0, 0)) {
			// 第2.3.1节，MTIME的零值表示未设置
			// 修改时间。
			le.PutUint32(z.buf[4:8], uint32(z.ModTime.Unix()))
		}
		if z.level == BestCompression {
			z.buf[8] = 2
		} else if z.level == BestSpeed {
			z.buf[8] = 4
		}
		z.buf[9] = z.OS
		_, z.err = z.w.Write(z.buf[:10])
		if z.err != nil {
			return 0, z.err
		}
		if z.Extra != nil {
			z.err = z.writeBytes(z.Extra)
			if z.err != nil {
				return 0, z.err
			}
		}
		if z.Name != "" {
			z.err = z.writeString(z.Name)
			if z.err != nil {
				return 0, z.err
			}
		}
		if z.Comment != "" {
			z.err = z.writeString(z.Comment)
			if z.err != nil {
				return 0, z.err
			}
		}
		if z.compressor == nil {
			z.compressor, _ = flate.NewWriter(z.w, z.level)
		}
	}
	z.size += uint32(len(p))
	z.digest = crc32.Update(z.digest, crc32.IEEETable, p)
	n, z.err = z.compressor.Write(p)
	return n, z.err
}

// Flush将所有挂起的压缩数据刷新到基础写入程序。
// 
// 它主要用于压缩网络协议，以确保远程读取器有足够的数据来重建数据包。Flush不返回
// 直到数据被写入。如果基础
// writer返回错误，Flush将返回该错误。
// 
// 在zlib库的术语中，Flush相当于Z_SYNC_Flush。
func (z *Writer) Flush() error {
	if z.err != nil {
		return z.err
	}
	if z.closed {
		return nil
	}
	if !z.wroteHeader {
		z.Write(nil)
		if z.err != nil {
			return z.err
		}
	}
	z.err = z.compressor.Flush()
	return z.err
}

// Close通过将任何未写入的数据刷新到基础
// io来关闭写入程序。编写并编写GZIP页脚。
// 它不会关闭基础io。作家
func (z *Writer) Close() error {
	if z.err != nil {
		return z.err
	}
	if z.closed {
		return nil
	}
	z.closed = true
	if !z.wroteHeader {
		z.Write(nil)
		if z.err != nil {
			return z.err
		}
	}
	z.err = z.compressor.Close()
	if z.err != nil {
		return z.err
	}
	le.PutUint32(z.buf[:4], z.digest)
	le.PutUint32(z.buf[4:8], z.size)
	_, z.err = z.w.Write(z.buf[:8])
	return z.err
}
